/*
 *  Copyright 2008 Mark Ashworth <javameme@gmail.com>.
 * 
 *  Licensed under the GNU General Public License v3 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.gnu.org/licenses/gpl-3.0.html
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package xperiment.metaphor.model.entity;

import java.io.Serializable;
import xperiment.metaphor.model.*;
import java.util.*;

import metaphor.service.ApplicationManager;
import metaphor.service.ServiceException;
import metaphor.persistence.Dao;
import metaphor.persistence.filter.Query;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import metaphor.persistence.filter.SimpleExpression;

/**
 * A user role.
 * @author Mark Ashworth
 * @version 1.0.0
 */
//@Entity
//@Table(name = "users")
public class User extends DomainObject implements UserRole, Persistent {
	private static final long serialVersionUID = 1L;
	/**
     * The role name of the actor - role collaboration
     */
    public static final String ROLE_NAME = "ROLE_AUTHENTICATED";
    /**
     * An empty user object
     */
    public static final User EMPTY = new User();    
    
    /* The unique database id for the object */
    //@Id @GeneratedValue(strategy=GenerationType.AUTO)
    //@Column(name = "user_id", unique = true, nullable = false)
    private Long id = null;
    
    /* The username of the user account */
    //@Column(name = "username", nullable = false, length = 25, unique=true)
    private String username;
    
    /* The user's password */
    //@Column(name = "password", nullable = false, length = 125)
	private String password;
   
    /** Whether the user is enabled */
    //@Column(name = "enabled", nullable=true)
    private boolean enabled = true;

    /* Last login history */
    private LoginHistory lastLogin;

    /* The last authentication event recorded for the user*/
    //@Transient
    private List<AuthenticationEvent> authenticationEvents = null;
	/**
	 * Default constructor
	 */
	public User() {
		this(null, null);
	}

	/**
	 * Constructs the user with the given username and password
	 * @param name The user's name
	 * @param password The user's password
	 */
	public User(String name, String password) {
		this.username = name;
		this.password = password;
	}
    
    /**
     * The collaboration role of the User to the Person 
     * @return String
     */
    public String getRole() {
        return ROLE_NAME;
    }
    
    /**
     * The unique database id for the object
     * @return long
     */
    public Long getId() {
        return id;
    }
     
	/**
     * The username of the user account
     * @return java.lang.String
     */
    public String getUsername() {
        return username;
    }
    
    /**
     * The username of the user account
     * @param name The new name of the object
     */
    public void setUsername(String name) {
        String old = this.username;
        this.username = name;        
        firePropertyChange("username", old, this.username);
    }
    
    /**
     * The name of the object
     * @return java.lang.String
     */
    public String getName() {
        return getUsername();
    }
    
	/**
     * The user's password
     * @return String
     */
	public String getPassword() {
		return password;
	}

    /**
     * The user's password
     * @param password The new value
     */
	public void setPassword(String password) {
		String old = this.password;
		this.password = password;
		firePropertyChange("password", old, this.password);
	}
    
    /**
     * Returns the last login for the user
     * @return LoginHistory
     */
    public LoginHistory getLastLogin() {
        return this.lastLogin;
    }

    /**
     * Whether the user is enabled
     * @return boolean
     */
    public boolean isEnabled() {
        return this.enabled;
    }

    /**
     * Whether the user is enabled
     * @param enabled The new value
     */
    public void setEnabled(boolean enabled) {
        boolean old = this.enabled;
		this.enabled = enabled;
		firePropertyChange("enabled", old, this.enabled);
    }
    
//    
//    /**
//     * The confirm password is not persisted but can be used by validator
//     * to determine if the password and the confirm passwords match before
//     * saving the user.
//     * @return String
//     */
//    @Transient
//    public String getConfirmPassword() {
//		return confirmPassword;
//	}
//
//    /**
//     * The confirm password is not persisted but can be used by validator
//     * to determine if the password and the confirm passwords match before
//     * saving the user.
//     * @param password The new confirm password value
//     */
//	public void setConfirmPassword(String password) {
//		String old = getConfirmPassword();
//		this.confirmPassword = password;
//		firePropertyChange("confirmPassword", old, this.confirmPassword);
//	}
//    
//    /**
//     * The user's roles
//     * @return List<Role>
//     */
//	public List<Role> getRoles() {
//		if (this.roles == null) {
//			this.roles = new HashSet<Role>();
//		}
//		return Collections.unmodifiableList(new ArrayList(this.roles));
//	}
//    
//    /**
//     * Sets the roles
//     * @param roles The new roles of the user'
//     */
//    public void setRoles(Collection<Role> roles) {
//        if (this.roles == null) {
//			this.roles = new HashSet<Role>();
//		}
//        this.roles.clear();
//        this.roles.addAll(roles);
//	}
//	
//    /**
//     * Adds a role
//     * @param role The new role
//     */
//	public void addRole(Role role) {
//        if (this.roles == null) {
//			this.roles = new HashSet<Role>();
//		}
//		this.roles.add(role);
//	}
//	
//    /**
//     * Adds the roles to user's roles
//     * @param roles The new roles to add
//     */
//	public void addRoles(Collection<Role> roles) {
//        if (this.roles == null) {
//			this.roles = new HashSet<Role>();
//		}
//		this.roles.addAll(roles);
//	}
//    
//    /**
//     * Removes all the roles from the user
//     */
//	public void clearRoles() {
//		if (this.roles != null) {
//			this.roles.clear();
//		}
//	}
//	
//    /**
//     * Removes a role from the user
//     * @param role The role to remove
//     * @return Whether the role was found and removed
//     */
//	public boolean remove(Role role) {
//        if (this.roles == null) {
//			this.roles = new HashSet<Role>();
//		}
//		return this.roles.remove(id);
//	}
//	
//    /**
//     * Removes the roles from the user that are found in the list
//     * @param roles The roles to removed from the user
//     */
//	public void removeRoles(Collection<Role> roles) {
//		getRoles().removeAll(roles);
//	}

//    /**
//     * The date and time that the user logged on
//     * @return Date
//     */
//    @Column(name = "logged_on", nullable=true)
//    public Date getLoggedOn() {
//        return loggedOn;
//    }
//
//    /**
//     * The date and time that the user last logged on
//     * @param lastLoggedOn The new value
//     */
//    public void setLoggedOn(Date loggedOn) {
//        Date old = getLastLoggedOn();
//        this.loggedOn = loggedOn;
//        firePropertyChange("loggedOn", old, getLoggedOn());
//    }
//    
//    /**
//     * The date and time that the user last logged on
//     * @return Date
//     */
//    @Column(name = "last_logged_on", nullable=true)
//    public Date getLastLoggedOn() {
//        return lastLoggedOn;
//    }
//
//    /**
//     * The date and time that the user last logged on
//     * @param lastLoggedOn The new value
//     */
//    public void setLastLoggedOn(Date lastLoggedOn) {
//        Date old = getLastLoggedOn();
//        this.lastLoggedOn = lastLoggedOn;
//        firePropertyChange("lastLoggedOn", old, getLastLoggedOn());
//    }
//
//    /**
//     * The IP Address that the user logged in from
//     * @return String
//     */
//    @Column(name = "ip_address", nullable=true, length=25)
//    public String getIPAddress() {
//        return ipAddress;
//    }
//
//    /**
//     * The IP Address that the user logged in from
//     * @param ipAddress The new value
//     */
//    public void setIPAddress(String ipAddress) {
//        String old = getIPAddress();
//        this.ipAddress = ipAddress;
//        firePropertyChange("iPAddress", old, getIPAddress());
//    }
//    
//    /**
//     * The last IP Address that the user logged in from
//     * @return String
//     */
//    @Column(name = "last_ip_address", nullable=true, length=25)
//    public String getLastIPAddress() {
//        return lastIPAddress;
//    }
//
//    /**
//     * The last IP Address that the user logged in from
//     * @param lastIPAddress The new value
//     */
//    public void setLastIPAddress(String lastIPAddress) {
//        String old = getLastIPAddress();
//        this.lastIPAddress = lastIPAddress;
//        firePropertyChange("lastIPAddress", old, getLastIPAddress());
//    }
//
//    /**
//     * The last session id
//     * @return String
//     */
//    @Column(name = "session", nullable=true, length=25)
//    public String getSession() {
//        return session;
//    }
//
//    /**
//     * The session id
//     * @param session The new value
//     */
//    public void setSession(String session) {
//        this.session = session;
//    }
//   
//    /**
//     * The person associated with the user account
//     * @return Person
//     */
//    @OneToOne(fetch=FetchType.EAGER)
//    public Person getPerson() {
//        return person;
//    }
//
//    public void setPerson(Person person) {
//        Person old = getPerson();
//        this.person = person;
//        firePropertyChange("person", old, getPerson());
//    }
//    
//    /*
//     * Spring Security
//     */
//    @Transient
//    public GrantedAuthority[] getAuthorities() {
//        Collection<Role> r = getRoles();
//        GrantedAuthority[] authorities = new GrantedAuthority[r.size()];
//        
//        int i = 0;
//        for (Role role : r) {
//            authorities[i++] = (GrantedAuthority) role;
//        }
//        return authorities;
//    }
//
//    /**
//     * Spring Security:<br />
//     * Whether the account is not expired. Setting the user.enabled to false
//     * sets the account to expired.
//     * @return boolean
//     */
//    @Transient
//    public boolean isAccountNonExpired() {
//        return isEnabled();
//    }
//
//    /**
//     * Spring Security:<br />
//     * Whether the account is not locked. Returns true.
//     * @return boolean
//     */
//    @Transient
//    public boolean isAccountNonLocked() {
//        return true;
//    }
//
//    /**
//     * Spring Security:<br />
//     * Whether the credentials are non expired. Returns true.
//     * @return boolean
//     */
//    @Transient
//    public boolean isCredentialsNonExpired() {
//        return true;
//    }

    /**
     * Retrieves the objects from the persistent store
     * @param applicationManager The application manager
     * @param all Whether all the objects should be included and not just enabled objects
     * @return List
     * @throws xperiment.metaphor.model.ModelException If the list of objects could not be retrieved
     */
    public static List retrieve(ApplicationManager applicationManager, boolean all) throws ModelException {

        return retrieve(applicationManager, User.class, all);
    }

    /**
     * Retrieves the user by id
     * @param applicationManager The application manager
     * @param id The id of the user to retrieve
     * @return User
     * @throws xperiment.metaphor.model.ModelException If the user could not be retrieved
     */
    public static User retrieveById(ApplicationManager applicationManager, Serializable id) throws ModelException {
        return (User) retrieveById(applicationManager, User.class, id);
    }

    /**
     * Retrieves the user by username
     * @param applicationManager The application manager
     * @param username The username of the user to retrieve
     * @return User
     * @throws xperiment.metaphor.model.ModelException If the user could not be retrieved
     */
    public static User retrieveByUserName(ApplicationManager applicationManager, String username) throws ModelException {

        try {
            Dao dao = (Dao) applicationManager.getDao();
            Query query = Query.forClass(User.class)
                    .add(SimpleExpression.ilike("username", username))
                    .add(SimpleExpression.eq("enabled", Boolean.TRUE));

            List values = dao.retrieve(query);
            if (values != null && values.size() == 1) {
                return (User) values.get(0);
            } else if (values != null && values.size() == 0) {
                return null;
            } else if (values == null) {
                return null;
            } else if (values.size() > 1) {
                throw new RetrieveException("The return values contained multiple values for username `" + username + "`");
            }
            return null;
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve user by username", e);
        }
    }

    public User retrieveById(Long id) throws ModelException {
        return (User) retrieveById(getApplicationManager(), id);
    }

    public User retrieveByUserName(String username) throws ModelException {
        return retrieveByUserName(getApplicationManager(), username);
    }

    /**
     * Retrieves the users
     * @param all Whether all users should be included and not just enabled users
     * @return List
     * @throws xperiment.metaphor.model.ModelException If the list of users could not be retrieved
     */
    public List retrieve(boolean all) throws ModelException {
        return super.retrieve(User.class, all);
    }

    /**
     * Saves the user
     */
    public User save() throws ModelException {
        /*
         * Persist the gender
         */
        try {
            Dao dao = getDao();

            List values = dao.retrieve(Query.forClass(User.class).add(SimpleExpression.ilike("username", getUsername())));
            if (values != null && values.size() == 1) {

                /* Set the id for the persist */
                User u = (User) values.get(0);
                this.id = u.getId();

                /* Persist the user with the new values */
                u = (User) dao.persist(this);

            } else if (values != null && values.size() == 0) {

                /* User does not exist so persist a new record */
                User u = (User) dao.persist(this);
                this.id = u.getId();

            } else if (values != null && values.size() > 1) {
                throw new SaveException("Multiple users found for username " + getUsername());
            } else {
                throw new SaveException("Null values returned from retrieve query");
            }

            return this;
        } catch (ServiceException e) {
            throw new SaveException("Unable to save user", e);
        }
    }

    /**
     * Records the authentication event against the user and updates the
     * login history.
     * 
     * @param event The authentication event to record
     * @throws ModelException If the event could not be recorded
     */
    public void record(AuthenticationEvent event) throws ModelException {

        if (event == null) {
            logger.warn("The authentication event to record for user was null");
            return;
        }

        /* Save the authentication event */
        event.attach(getApplicationManager());
        event.save();

        /* Save the user */
        if (event.getUserName() == null) {
            return;
        }

        User user = retrieveByUserName(event.getUserName());
        if (user != null) {
            user.lastLogin = new LoginHistory(event.getEventTime(), event.getRemoteAddress());
            user.attach(getApplicationManager());
            user = user.save();

            this.lastLogin = user.getLastLogin();
        }
    }

    /**
     * Loads the authentication events for the user
     */
    public void loadAuthenticationEvents() throws ModelException {

        try {
            Dao dao = getDao();
            Query query = Query.forClass(AuthenticationEvent.class)
                    .add(SimpleExpression.ilike("username", getUsername()))
                    .add(SimpleExpression.eq("enabled", Boolean.TRUE));

            authenticationEvents = (List<AuthenticationEvent>) dao.retrieve(query);
        } catch (ServiceException e) {
            throw new SaveException("Unable to retrieve login history for user", e);
        }
    }

    /**
     * Returns the user name as the representation of the user
     * @return String
     */
    @Override
    public String toString() {
        return getUsername();
    }
    
    /**
     * Returns the XML representation of the user.<br />
     * &lt;user_login class="xperiment.metaphor.model.user.User" user_name="administrator" password="password"&gt;<br />
	 * 	&lt;role class="xperiment.metaphor.model.user.Role" name="ROLE_USER"/&gt;<br />
	 * 	&lt;role class="xperiment.metaphor.model.user.Role" name="ROLE_ADMINISTRATOR"/&gt;<br />
	 * &lt;/user_login&gt;<br />
     * @return String 
     */
    public String toXML() {
        
        StringBuffer buf = new StringBuffer();
//        for (Role role : roles) {
//            if (role != null) {
//                buf.append(role.toXML());
//            }
//        }
        
        return "<user_login class='" + getClass().getName() 
                + "' user_name='" + getUsername() 
                + "' password='" + getPassword() 
                + "' enabled='" + isEnabled()
                +"' >"
                + (getLastLogin() != null ? getLastLogin().toXML() : "")
                + buf.toString()
                + "</user_login>";
    }
    
	/**
	 * Equality based upon the names of the two objects. The comaprison is
	 * case-sensitive.
	 * @param obj The other object
	 * @return boolean
	 */
	@Override
    public boolean equals(Object obj) {
        if (obj == null) { return false; }
        if (obj == this) { return true; }
        if (obj.getClass() != getClass()) {
            return false;
        }
        User rhs = (User) obj;
        return new EqualsBuilder()
            .append(this.username, rhs.getUsername())
            .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 39).append(this.username).toHashCode();
    }

    public int compareTo(Object o) {

        User other = (User) o;
        return new CompareToBuilder()
                .append(this.username, other.getUsername())
                .toComparison();
    }
}
